gl: add --icaptcha-proof flag to register command#193
Conversation
This allows users to provide a pre-obtained iCaptcha proof token when registering with a node, sent as the x-icaptcha-proof header on the initial POST /api/register request. Usage: gl register --icaptcha-proof "<token>" GITLAWB_ICAPTCHA_PROOF="<token>" gl register The existing automatic iCaptcha retry flow (403 -> solve -> retry) remains unchanged and is still available if no initial proof is provided.
|
Thanks for the contribution. A couple of things will help us review this faster:
See CONTRIBUTING.md. Update the PR and these notes will clear automatically. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughRegistration now accepts an optional iCaptcha proof from CLI or environment configuration and sends it through a proof-aware signed POST flow. Existing iCaptcha challenge detection, solving, and retry behavior remains available. ChangesiCaptcha registration support
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant RegisterArgs
participant NodeClient
participant NodeAPI
CLI->>RegisterArgs: Read iCaptcha proof
RegisterArgs->>NodeClient: post_with_proof(/api/register, body, proof)
NodeClient->>NodeAPI: Send signed POST with proof
NodeAPI-->>NodeClient: Return response or iCaptcha challenge
NodeClient->>NodeAPI: Solve challenge and retry when required
Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/gl/src/register.rs (1)
148-156: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test that verifies the proof header is forwarded.
All three existing tests pass
icaptcha_proof: None, so no test exercises the newpost_with_proofpath with an actual proof. A mockito test asserting thex-icaptcha-proofheader is present whenicaptcha_proofisSome(...)would protect this new feature path against regressions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/gl/src/register.rs` around lines 148 - 156, Add a register test near the existing run/RegisterArgs tests that sets icaptcha_proof to Some(...) and configures the mock server to assert the x-icaptcha-proof header value, thereby exercising the post_with_proof path while preserving the existing no-proof coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/gl/src/register.rs`:
- Around line 148-156: Add a register test near the existing run/RegisterArgs
tests that sets icaptcha_proof to Some(...) and configures the mock server to
assert the x-icaptcha-proof header value, thereby exercising the post_with_proof
path while preserving the existing no-proof coverage.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b6f3668d-c02b-4b0a-bbe5-e4396b941cef
📒 Files selected for processing (2)
crates/gl/src/http.rscrates/gl/src/register.rs
beardthelion
left a comment
There was a problem hiding this comment.
Reviewed the client change end to end against a mock node, driving both the granted and the denied/hostile paths. The core is sound and the trust boundary is respected: a pre-supplied proof rides only the first request and does not change the retry budget (1 initial + MAX_ICAPTCHA_RETRIES), a stale token is discarded and re-solved rather than looped, a malformed proof value is rejected at header build instead of being sent, and a bogus proof grants nothing the automatic solve path doesn't since the node re-verifies the iCaptcha signature and the sub/DID binding. Requesting changes for one missing test on the new path plus two minor cleanups; none of these are correctness or security blockers.
What I verified by execution (both directions): valid proof forwards on the first request with zero solves; a bogus proof against an always-403 node stays bounded at exactly 3 requests / 2 solves, identical to the no-proof path; the retry carries the freshly solved proof, not the stale user token; Some("") sends a present-but-empty header (distinct from the omitted None path); and CR/LF/NUL in the token fail with builder error: failed to parse header value rather than being sent.
Findings
-
[P3] Add a test that the proof is forwarded on the first request.
crates/gl/src/register.rs:57/crates/gl/src/http.rs:119— the newpost_with_proof->send_signed_with_proof(initial_proof)branch ships with no coverage: all three register fixtures passicaptcha_proof: None, and everyhttp.rstest drivessend_signed(theNonepath), so nothing exercises aSome(proof)value reaching the first request. A regression that dropped or mis-threaded the initial proof would stay green. A drop-in that mirrors the existingtest_register_success_saves_ucansetup:#[tokio::test] async fn register_forwards_icaptcha_proof_header() { let dir = TempDir::new().unwrap(); write_identity(&dir); let mut server = mockito::Server::new_async().await; let m = server .mock("POST", "/api/register") .match_header("x-icaptcha-proof", "pre.solved.token") .with_status(200) .with_header("content-type", "application/json") .with_body(r#"{"message":"Welcome","ucan":"eyJhbGci.test.token","trust_score":0.5,"expires":"2026-12-31"}"#) .expect(1) .create_async() .await; run(RegisterArgs { node: server.url(), capabilities: vec!["git:push".to_string()], model: None, dir: Some(dir.path().to_path_buf()), icaptcha_proof: Some("pre.solved.token".to_string()), }) .await .unwrap(); m.assert(); assert!(dir.path().join("ucan.json").exists()); }
I ran this against the branch and it passes; flipping the matched header value makes it fail, so it is load-bearing.
-
[P3] Document the
--icaptcha-proofflag.crates/gl/src/register.rs:20— the field carries#[arg(long, env = "GITLAWB_ICAPTCHA_PROOF")]but no///doc comment, unlike every sibling inRegisterArgs. clap renders the field doc as the flag's help text, sogl register --helplists--icaptcha-proof <ICAPTCHA_PROOF>with an empty description. One line describing that it accepts a pre-obtained iCaptcha proof token fixes it. -
[P3] Don't report a malformed proof as a connection failure.
crates/gl/src/register.rs:59— a token containing bytes outside the header grammar fails at header build, but.context("failed to connect to node")surfaces it asfailed to connect to node: ... builder error: failed to parse header value, which points the user at their network rather than their input. Reachable now that the token is user-supplied. Either validate/trim the token at the CLI boundary or give this call site a context that doesn't assert a connection error.
The rest checks out: post/put/delete are behaviorally unchanged (they delegate with None), the full gl suite stays green, and the PR body's claim that the automatic retry flow is unchanged is accurate.
jatmn
left a comment
There was a problem hiding this comment.
I found issues that need to be addressed before this is ready.
Findings
-
[P3] Add help text for the new proof flag
crates/gl/src/register.rs:20
The field has no doc comment, so Clap renders--icaptcha-proof <ICAPTCHA_PROOF>with an empty description (only the environment-variable annotation). This is the only CLI discovery surface for the newly added user-facing token input, leaving users unable to tell what the flag expects. Add a concise description that it accepts a pre-obtained iCaptcha proof token. -
[P3] Cover the supplied-proof registration path
crates/gl/src/register.rs:57
Everyregister::testsfixture setsicaptcha_proof: None; the existinghttp.rsproof test exercises the lower-levelsend_oncehelper rather thanRegisterArgs -> post_with_proof. A regression that drops or misthreads the new CLI value would therefore leave the registration suite green and make the advertised workaround fail. Add a mocked registration test withSome(...)that matches the initial request'sx-icaptcha-proofheader. -
[P3] Do not label malformed proof input as a connection failure
crates/gl/src/register.rs:57
A proof containing CR/LF is rejected locally while Reqwest builds the header, before any request is sent, but the surrounding context reports it asfailed to connect to node. This new user-controlled error path sends users to debug node/network connectivity instead of the supplied token. Validate the proof at the CLI boundary or use neutral request context so invalid header values are reported accurately.
|
Thanks for the thorough review, @beardthelion and @jatmn. I've addressed all three [P3] items:
The HTTP client (
All local tests pass. Ready for another look whenever you have time. |
jatmn
left a comment
There was a problem hiding this comment.
I found issues that need to be addressed before this is ready.
Findings
-
[P1] Do not forward a supplied proof across redirects
crates/gl/src/http.rs:25-30
NodeClientuses Reqwest's default redirect policy, so a 307/308 response to the new initial proof-bearingPOST /api/registerresends thex-icaptcha-proofheader to theLocationorigin. Reqwest only removes its small built-in authorization/cookie header set on cross-host redirects; this custom proof header (and the signed request headers) remains attached. This expands a user-supplied proof beyond the configured node and lets a redirecting endpoint expose a fresh, DID-bound proof and replayable registration request to an arbitrary origin. Disable redirects for signed/proof-bearing writes (or explicitly reject cross-origin redirects) and cover that behavior. -
[P1] Restore compilation of the existing registration tests
crates/gl/src/register.rs:151
Adding the requiredicaptcha_prooffield left the existingRegisterArgsliterals at lines 151, 183, and 202 incomplete. As a result,cargo test -p gl registerfails to compile withE0063before any test runs, including the new proof-forwarding test. Addicaptcha_proof: Noneto the pre-existing fixtures (and update any other direct initializers).
This allows users to provide a pre-obtained iCaptcha proof token when registering with a node, sent as the x-icaptcha-proof header on the initial POST /api/register request.
Usage:
gl register --icaptcha-proof "<token>"GITLAWB_ICAPTCHA_PROOF="<token>" gl registerThe existing automatic iCaptcha retry flow (403 -> solve -> retry) remains unchanged and is still available if no initial proof is provided.
Summary
Add an optional
--icaptcha-proof / GITLAWB_ICAPTCHA_PROOFargument togl registerthat sends the proof as the x-icaptcha-proof header on the initial /api/register request.This is a minimal compatibility fix for nodes running with icaptcha enforcement, without yet implementing the full automatic challenge/solve/retry flow.
Motivation & context
When registering an agent with a node that has iCaptcha enforcement enabled, the current gl register command has no way to provide a pre-obtained proof token. This forces users to either:
Disable iCaptcha on the node (reduces security), or
Manually modify the CLI source to add the header
issue #190
Kind of change
What changed
crates/gl/src/register.rscrates/gl/src/http.rsHow a reviewer can verify
replace the crates, fmt and build then run
gl register --icaptcha-proof "your-proof"a new proof from theapi/register/endpoint (i used postman to get the process done)Before you request review
cargo test --workspacepasses locallycargo fmt --allandcargo clippy --workspace --all-targets -- -D warningsare cleanfeat(...),fix(...),docs(...)).env.exampleupdated if behavior or config changed (or N/A)Notes for reviewers
simplify the process of registering new agents without touching your codebase
Summary by CodeRabbit